/**
 * ClassIntrospector.java		26/06/2009
 */
package org.jopris;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.security.ProtectionDomain;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.jopris.util.NameHelper;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;

/**
 * This class introspect any new class before it is loaded. The introspector
 * looks for all {@link Property} references within the byte-code and register
 * it within local repository.
 * 
 * @author Guy Nir
 * @version 1.00
 * 
 */
public class ClassIntrospector implements ClassFileTransformer {

	private final AnnotatedFieldsRepository repository;

	private static final Map<String, String> MUTABLE_EMPTY_MAP = Collections.unmodifiableMap(new HashMap<String, String>());
	private static final String GETTER_ANNOTATION_FIELD_NAME = "getter";
	private static final String SETTER_ANNOTATION_FIELD_NAME = "setter";
	private static final String PROPERTY_DESCRIPTION = NameHelper.toInternalClassName(Property.class);

	/**
	 * Class constructor.
	 */
	public ClassIntrospector(AnnotatedFieldsRepository repository) {
		this.repository = repository;
	}

	@SuppressWarnings("unchecked")
	public byte[] transform(ClassLoader loader,
			String className,
			Class<?> classBeingRedefined,
			ProtectionDomain protectionDomain,
			byte[] classfileBuffer) throws IllegalClassFormatException {

		ClassReader reader = new ClassReader(classfileBuffer);
		ClassNode classNode = new ClassNode();
		reader.accept(classNode, 0);

		processClassFields((List<FieldNode>) classNode.fields, classBeingRedefined);

		return null;
	}

	@SuppressWarnings("unchecked")
	private void processClassFields(List<FieldNode> fields, Class<?> clazz) {
		if (fields == null) {
			return;
		}

		for (FieldNode fieldNode : fields) {
			AnnotationNode annotationNode = getPropertyAnnotationNode(fieldNode);
			if (annotationNode == null) {
				continue;
			}

		}
	}

	private void processFieldNode(FieldNode fieldNode, Class<?> clazz) {
		if (fieldNode == null) {
			return;
		}
		AnnotationNode annotationNode = getPropertyAnnotationNode(fieldNode);
		if (annotationNode != null) {
			processAnnotationNode(annotationNode, clazz);
		}
	}
	
	private Map<String,String> getAnnotationFields(FieldNode fieldNode) {
		Map<String, String> map = new HashMap<String, String>(2);
		AnnotationNode annotationNode = getPropertyAnnotationNode(fieldNode);
		if (annotationNode != null) {
			for 
		}
		
		return map;
	}

	@SuppressWarnings("unchecked")
	private AnnotationNode getPropertyAnnotationNode(FieldNode node) {
		AnnotationNode result = null;
		if (node.visibleAnnotations != null) {
			for (AnnotationNode annotationNode : (List<AnnotationNode>) node.visibleAnnotations) {
				if (annotationNode.desc.equals(PROPERTY_DESCRIPTION)) {
					result = annotationNode;
					break;
				}
			}
		}

		return result;
	}

	@SuppressWarnings("unchecked")
	private void processAnnotationNode(AnnotationNode annotationNode, Class<?> clazz) {
		String getterMethodName = null;
		String setterMethodName = null;
		if (annotationNode.values != null) {
			Iterator<String> itr = ((List<String>) annotationNode.values).iterator();
			while (itr.hasNext()) {
				String type = itr.next();
				if (type.equals(GETTER_ANNOTATION_FIELD_NAME)) {
					getterMethodName = itr.next();
				} else if (type.equals(SETTER_ANNOTATION_FIELD_NAME)) {
					setterMethodName = itr.next();
				}
			}

			try {
				repository.addAnnotatedField(clazz, fieldNode.name, fieldNode.desc, getterMethodName, setterMethodName);
			} catch (RuntimeException ex) {
				ex.printStackTrace();
			} catch (NoSuchMethodException ex) {
				System.out.println("Invalid @Property value: " + ex.getMessage());
				ex.printStackTrace();
			}
		}
	}
}
